/*
 * Copyright 2012 Marsrutai.lt
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.marsrutai.mobile.android.adapter;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lt.marsrutai.mobile.android.AppContext;
import lt.marsrutai.mobile.android.R;
import lt.marsrutai.mobile.android.model.ArrivalTime;
import lt.marsrutai.mobile.android.model.Bookmark;
import lt.marsrutai.mobile.android.model.StopBookmark;
import lt.marsrutai.mobile.android.task.GetArrivalTimesTask;
import lt.marsrutai.mobile.android.util.NextDepartures;
import lt.marsrutai.mobile.android.util.TimeUntilNextDeparture;
import android.content.Context;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class StopBookmarkAdapter<T extends StopBookmark> extends BookmarkAdapter<T> {

    protected Map<Bookmark, List<ArrivalTime>> arrivalTimeMap = new HashMap<Bookmark, List<ArrivalTime>>();
    protected Map<Bookmark, String> bookmarkValueCache = new HashMap<Bookmark, String>();

    public StopBookmarkAdapter(Context context) {
        super(context, R.layout.bookmark_stop_item);
    }
    
    public StopBookmarkAdapter(Context context, int viewResourceId) {
        super(context, viewResourceId);
    }

    @Override
    public View doGetView(final int position, View view, final ViewGroup parent) {

        T bookmark = getItem(position);

        if (bookmark != null) {
            TextView stopName = findViewById(view, R.id.stop_name);
            stopName.setText(bookmark.getStopName());

            TextView directionName = (TextView) findViewById(view, R.id.direction_name);
            directionName.setText(bookmark.getStopDirectionName());

            clearReusedViewInfo(view);
            
            displayArrivalTimes(view, bookmark);
        }

        return super.doGetView(position, view, parent);
    }
    
    /**
     * Uses a custom tag to cache views.
     */
    @SuppressWarnings("unchecked")
    @Override
    protected <V extends View> V findViewById(View view, int id) {
        Tag tag = getCreateTag(view);
        tag.viewHoler.append(id, view.findViewById(id));
        
        return (V) tag.viewHoler.get(id);
    }

    /**
     * Tags a task with the provided view
     * 
     * @param view
     * @param task
     */
    protected void tagTask(View view, BookmarkTask task) {
        Tag tag = getCreateTag(view);
        
        tag.task = task;
    }

    /**
     * Displays arrival times if they are already in the cache. Otherwise starts
     * an async task to fetch them
     * 
     * @param view
     * @param bookmark
     * @return
     */
    protected void displayArrivalTimes(View view, T bookmark) {
        if (arrivalTimeMap.containsKey(bookmark)) {
            List<ArrivalTime> arrivalTimes = arrivalTimeMap.get(bookmark);
            populateArrivalTimeInfo(view, bookmark, arrivalTimes);
        }
        else {
            // cancel previous download if the view was recycled
            if (cancelPotentialDownload(view, bookmark)) {
                BookmarkTask task = startTask(view, bookmark);
                tagTask(view, task);
            }
        }
    }

    /**
     * Starts arrival time download task
     * 
     * @param view
     * @param bookmark
     * @return
     */
    protected BookmarkTask startTask(View view, T bookmark) {
        GetTransportStopArrivalTimesTask task = new GetTransportStopArrivalTimesTask(view, bookmark);
        
        task.execute(new GetArrivalTimesTask.Criteria(bookmark.getScopeFolder(), bookmark.getStopName(), bookmark
                .getStopId(), null, 600, 20));
        
        return task;
    }

    /**
     * Populates arrival time info
     * 
     * @param view
     * @param bookmark
     * @param arrivalTimes
     */
    protected void populateArrivalTimeInfo(View view, Bookmark bookmark, List<ArrivalTime> arrivalTimes) {
        String value = bookmarkValueCache.get(bookmark);
        if (value == null) {
            value = new NextDepartures(arrivalTimes, true).toString();
            bookmarkValueCache.put(bookmark, value);
        }
        
        TextView nextStopTimes = findViewById(view, R.id.next_stop_times);
        nextStopTimes.setText(value);

        TextView timeRemaining = findViewById(view, R.id.time_remaining);
        timeRemaining.setText(AppContext.getApplicationContext().getString(R.string.arrives_in,
                new TimeUntilNextDeparture(arrivalTimes, true).toString()));
    }

    /**
     * <p>
     * Each view that is to be displayed gets assigned a task responsible for
     * the download of various information.
     * </p>
     * <p>
     * This method makes sure that recycled views do not continue on
     * downloading information not related to this particular view.
     * </p>
     * 
     * @param view
     * @param bookmark
     * @return
     */
    protected boolean cancelPotentialDownload(View view, Bookmark bookmark) {
        Tag tag = getTag(view);
        BookmarkTask task = (tag == null || tag.task == null) ? null : tag.task;

        if (task != null) {
            if (task.getBookmark() != null && task.getBookmark().equals(bookmark)) {
                // relevant information is already being downloaded
                return false;
            }
            else {
                task.cancel(true);
            }
        }
        return true;
    }

    /**
     * Clears whatever information is in the reused view before an async
     * download
     * 
     * @param view
     */
    protected void clearReusedViewInfo(View view) {
    }
    
    /**
     * Gets a tag assigned to the provided view
     * 
     * @param view
     * @return
     */
    protected Tag getTag(View view) {
        return (Tag) view.getTag();
    }
    
    /**
     * Gets a tag assigned to the provided view, or creates a new empty tag if
     * the view doesn't have a tag assigned.
     * 
     * @param view
     * @return
     */
    protected Tag getCreateTag(View view) {
        Tag tag = getTag(view);
        
        if (tag == null) {
            tag = new Tag();
            view.setTag(tag);
        }
        return tag;
    }

    private class GetTransportStopArrivalTimesTask extends GetArrivalTimesTask implements BookmarkTask {

        private final WeakReference<View> textViewReference;
        private StopBookmark bookmark;

        public GetTransportStopArrivalTimesTask(View view, StopBookmark stopBookmark) {
            this.textViewReference = new WeakReference<View>(view);
            this.bookmark = stopBookmark;
        }

        @Override
        protected void onPostExecute(List<ArrivalTime> arrivalTimes) {
            if (isCancelled()) {
                return;
            }

            arrivalTimeMap.put(bookmark, arrivalTimes);

            if (textViewReference != null) {
                View view = textViewReference.get();
                
                if (view != null) {
                    Tag tag = getTag(view);
                    if (tag != null && tag.task == this) {
                        populateArrivalTimeInfo(view, bookmark, arrivalTimes);
                    }
                }
            }
        }

        @Override
        public Bookmark getBookmark() {
            return bookmark;
        }
    }
    
    /**
     * Custom view tab. Holds cached views and a {@link BookmarkTask}
     * 
     * @author Justas
     */
    protected static class Tag {
        
        public SparseArray<View> viewHoler = new SparseArray<View>();
        public BookmarkTask task;
    }
    
    /**
     * Interface for a generic type of task which downloads info for a specific
     * bookmark.
     * 
     * @author Justas
     */
    protected static interface BookmarkTask {
        
        /**
         * Returns the task bookmark.
         * 
         * @return
         */
        Bookmark getBookmark();
        
        boolean cancel(boolean mayInterruptIfRunning);
    }
}
